package org.bitterorange.flansmodslain;

import net.milkbowl.vault.economy.Economy;
import net.minecraftforge.common.MinecraftForge;
import org.bitterorange.flansmodslain.actions.Action;
import org.bitterorange.flansmodslain.actions.Point;
import org.bitterorange.flansmodslain.interaction.DebugCommand;
import org.bitterorange.flansmodslain.interaction.MainCommand;
import org.bitterorange.flansmodslain.interaction.PointsCommand;
import org.bitterorange.flansmodslain.listeners.*;
import org.bitterorange.flansmodslain.models.KeyValuePair;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scheduler.BukkitTask;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.nodes.Tag;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.util.*;
import java.util.logging.Logger;

import static org.bitterorange.flansmodslain.listeners.PlayerSlainListener.load;

public final class FlansModSlain extends JavaPlugin {
    public static final File PluginRoot = new File("./plugins/FlansModSlain");
    private static final Set<Material> MeleeWeapons = new HashSet<>();
    private static final Map<String, Action> Actions = new HashMap<>();
    private static final Map<Integer, List<Action>> LinkedKillActions = new HashMap<>();
    private static final Map<UUID, Integer> LinkedKillCount = new HashMap<>();
    private static final List<KeyValuePair<Integer, List<Action>>> DistantKillLevel = new ArrayList<>();
    private static final List<KeyValuePair<Integer, String>> RankLevel = new ArrayList<>();
    private static final Map<UUID, Integer> Points = new HashMap<>();
    private static Server Server;
    private static Plugin Plugin;
    private static Logger Logger;
    private static BukkitScheduler Scheduler;
    private static Economy VaultEconomy;

    public static File getPluginRoot() {
        return PluginRoot;
    }

    public static void loadConfigurations() {
        load();
        try {
            loadActions();
            Logger.info("actions.yml 加载成功，击杀 Action 模块已启用。");
        } catch (Exception e) {
            Logger.warning("actions.yml 加载异常！击杀 Action 模块可能不起作用。");
            e.printStackTrace();
        }
        try {
            loadLinkedKill();
            Logger.info("linkedkill.yml 加载成功，连杀模块已启用。");
        } catch (Exception e) {
            Logger.warning("linkedkill.yml 加载异常！连杀模块可能不起作用。");
            e.printStackTrace();
        }
        try {
            loadDistantKill();
            Logger.info("distantkill.yml 加载成功，远距离击杀模块已启用。");
        } catch (Exception e) {
            Logger.warning("distantkill.yml 加载异常！远距离模块可能不起作用。");
            e.printStackTrace();
        }
        try {
            loadRanks();
            Logger.info("ranks.yml 加载成功，军衔模块已启用。");
        } catch (Exception e) {
            Logger.warning("ranks.yml 加载异常！军衔模块可能不起作用。");
            e.printStackTrace();
        }
    }

    private static void loadActions() throws Exception {
        File file = new File(PluginRoot, "actions.yml");
        if (!file.exists()) {
            Plugin.saveResource("actions.yml", false);
        }
        //noinspection unchecked
        Map<String, Object> map = new Yaml().loadAs(new FileInputStream(file), Map.class);
        Actions.clear();
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            try {
                //noinspection unchecked
                List<String> lines = (List<String>) entry.getValue();
                Action action = new Action(entry.getKey(), lines);
                Actions.put(entry.getKey(), action);
            } catch (Exception e) {
                Logger.warning(e.getMessage());
                e.printStackTrace();
            }
        }
    }

    private static void loadLinkedKill() throws Exception {
        File file = new File(PluginRoot, "linkedkill.yml");
        if (!file.exists()) {
            Plugin.saveResource("linkedkill.yml", false);
        }
        //noinspection unchecked
        Map<Object, Object> map = new Yaml().loadAs(new FileInputStream(file), Map.class);
        LinkedKillActions.clear();
        for (Map.Entry<Object, Object> entry : map.entrySet()) {
            try {
                int from, to;
                if (entry.getKey() instanceof Integer) {
                    from = to = (int) entry.getKey();
                } else if (entry.getKey() instanceof String) {
                    String[] range = ((String) entry.getKey()).split("-");
                    if (range.length != 2) {
                        throw new IllegalArgumentException("“" + entry.getKey() + "”的格式错误。正确格式：from-to。例如：5000-6000。");
                    }
                    try {
                        from = Integer.parseInt(range[0]);
                        to = Integer.parseInt(range[1]);
                    } catch (NumberFormatException e) {
                        throw new NumberFormatException("“" + entry.getKey() + "”的格式错误。确保区间的两端都是整数");
                    }
                    if (from > to) {
                        throw new IllegalArgumentException("“" + entry.getKey() + "”的格式错误。请确保前面的整数小于等于后面的。");
                    }
                } else {
                    throw new IllegalArgumentException("“" + entry.getKey() + "”的格式错误。正确格式：xxx，或xxx-xxx。");
                }
                //noinspection unchecked
                List<String> actionIds = (List<String>) entry.getValue();
                List<Action> actions = new ArrayList<>();
                for (String actionId : actionIds) {
                    if (Actions.containsKey(actionId)) {
                        actions.add(Actions.get(actionId));
                    } else {
                        throw new Exception(String.format("读取“%s”项时发生异常：Action “%s”不存在。", entry.getKey(), actionId));
                    }
                }
                for (int i = from; i <= to; ++i) {
                    if (LinkedKillActions.containsKey(i)) {
                        LinkedKillActions.get(i).addAll(actions);
                    } else {
                        LinkedKillActions.put(i, new ArrayList<>(actions));
                    }
                }
            } catch (Exception e) {
                Logger.warning(e.getMessage());
                e.printStackTrace();
            }
        }
    }

    private static void loadDistantKill() throws Exception {
        File file = new File(PluginRoot, "distantkill.yml");
        if (!file.exists()) {
            Plugin.saveResource("distantkill.yml", false);
        }
        DistantKillLevel.clear();
        //noinspection unchecked
        Map<Integer, List<String>> map = new Yaml().loadAs(new FileInputStream(file), Map.class);
        Map<Integer, List<Action>> actionMap = new HashMap<>();
        for (Map.Entry<Integer, List<String>> entry : map.entrySet()) {
            for (String actionId : entry.getValue()) {
                try {
                    if (Actions.containsKey(actionId)) {
                        if (actionMap.containsKey(entry.getKey())) {
                            actionMap.get(entry.getKey()).add(Actions.get(actionId));
                        } else {
                            List<Action> a = new ArrayList<>();
                            a.add(Actions.get(actionId));
                            actionMap.put(entry.getKey(), a);
                        }
                    } else {
                        throw new Exception(String.format("读取“%s”项时发生异常：Action “%s”不存在。", entry.getKey(), actionId));
                    }
                } catch (Exception e) {
                    Logger.warning(e.getMessage());
                    e.printStackTrace();
                }
            }
        }
        actionMap.forEach((k, actions) -> DistantKillLevel.add(new KeyValuePair<>(k, actions)));
        DistantKillLevel.sort(Comparator.comparing(KeyValuePair::getKey));
    }

    public static void loadRanks() throws Exception {
        File file = new File(PluginRoot, "ranks.yml");
        if (!file.exists()) {
            Plugin.saveResource("ranks.yml", false);
        }
        RankLevel.clear();
        //noinspection unchecked
        Map<Integer, String> map = new Yaml().loadAs(new FileInputStream(file), Map.class);
        for (Map.Entry<Integer, String> entry : map.entrySet()) {
            RankLevel.add(new KeyValuePair<>(entry.getKey(), entry.getValue()));
        }
        RankLevel.sort(Comparator.comparing(KeyValuePair::getKey));
    }

    public static void loadItems() {
        try {
            loadMeleeWeapons();
            Logger.info("items/meleeweapons.yml 加载成功，插件应该知道哪些物品是近身武器了。");
        } catch (Exception e) {
            Logger.warning("items/meleeweapons.yml 加载异常！插件可能无法识别近身武器击杀。");
            e.printStackTrace();
        }
    }

    private static void loadMeleeWeapons() throws Exception {
        File itemDirectory = new File(PluginRoot, "items");
        if (!itemDirectory.exists()) {
            itemDirectory.mkdir();
        }
        File meleeWeaponsFile = new File(itemDirectory, "meleeweapons.yml");
        if (!meleeWeaponsFile.exists()) {
            Plugin.saveResource("items/meleeweapons.yml", false);
        }
        MeleeWeapons.clear();
        //noinspection unchecked
        Map<String, List<String>> map = new Yaml().loadAs(new FileInputStream(meleeWeaponsFile), Map.class);
        map.get("meleeweapons").forEach(s -> MeleeWeapons.add(Material.valueOf(s)));
    }

    public static Logger getPluginLogger() {
        return Logger;
    }

    public static Server getBukkitServer() {
        return Server;
    }

    public static Economy getVaultEconomy() {
        return VaultEconomy;
    }

    public static List<Action> actionsAtDistance(double distance) {
        if (DistantKillLevel.isEmpty() || distance < DistantKillLevel.get(0).getKey()) {
            return new ArrayList<>();
        }
        int level = 1;
        int size = DistantKillLevel.size();
        while (level - 1 < size && distance >= DistantKillLevel.get(level - 1).getKey()) {
            ++level;
        }
        return Collections.unmodifiableList(DistantKillLevel.get(level - 2).getValue());
    }

    public static void loadData() {
        try {
            loadLinkedKillData();
            Logger.info("连杀数据 data/linkedkilldata.yml 加载成功。");
        } catch (Exception e) {
            Logger.warning("连杀数据 data/linkedkilldata.yml 加载异常！");
            Logger.warning("请确保该数据可访问且正确，并在服务端启动后键入 /fms reload 以重新加载。");
            Logger.warning("否则，插件将在服务端关闭时重置该数据文件，原来的数据将丢失。");
            Logger.warning("当然，如果你不想保留原来的数据，那就当我没说吧。");
            e.printStackTrace();
        }
        try {
            loadPointData();
            Logger.info("连杀数据 data/pointdata.yml 加载成功。");
        } catch (Exception e) {
            Logger.warning("连杀数据 data/pointdata.yml 加载异常！");
            Logger.warning("请确保该数据可访问且正确，并在服务端启动后键入 /fms reload 以重新加载。");
            Logger.warning("否则，插件将在服务端关闭时重置该数据文件，原来的数据将丢失。");
            Logger.warning("当然，如果你不想保留原来的数据，那就当我没说吧。");
            e.printStackTrace();
        }
    }

    public static void loadLinkedKillData() throws Exception {
        File file = new File(PluginRoot, "data");
        if (!file.exists()) {
            //noinspection ResultOfMethodCallIgnored
            file.mkdir();
        }
        LinkedKillCount.clear();
        File linkedKillData = new File(file, "linkedkilldata.yml");
        if (!linkedKillData.exists()) {
            return;
        }
        //noinspection unchecked
        Map<String, Integer> map = new Yaml().loadAs(new FileInputStream(linkedKillData), Map.class);
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            LinkedKillCount.put(UUID.fromString(entry.getKey()), entry.getValue());
        }
    }

    public static void loadPointData() throws Exception {
        File file = new File(PluginRoot, "data");
        if (!file.exists()) {
            //noinspection ResultOfMethodCallIgnored
            file.mkdir();
        }
        Points.clear();
        File pointData = new File(file, "pointdata.yml");
        if (!pointData.exists()) {
            return;
        }
        //noinspection unchecked
        Map<String, Integer> map = new Yaml().loadAs(new FileInputStream(pointData), Map.class);
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            Points.put(UUID.fromString(entry.getKey()), entry.getValue());
        }
    }

    public static void saveData() {
        try {
            saveLinkedKillData();
            Logger.info("连杀数据 data/linkedkilldata.yml 保存成功！");
        } catch (Exception e) {
            Logger.warning("连杀数据 data/linkedkilldata.yml 保存失败！本次连杀数据可能已经丢失。");
            e.printStackTrace();
        }
        try {
            savePointData();
            Logger.info("连杀数据 data/pointdata.yml 保存成功！");
        } catch (Exception e) {
            Logger.warning("连杀数据 data/pointdata.yml 保存失败！本次连杀数据可能已经丢失。");
            e.printStackTrace();
        }
    }

    @SuppressWarnings("ResultOfMethodCallIgnored")
    private static void saveLinkedKillData() throws Exception {
        if (!PluginRoot.exists()) {
            PluginRoot.mkdir();
        }
        File dataDirectory = new File(PluginRoot, "data");
        if (!dataDirectory.exists()) {
            dataDirectory.mkdir();
        }
        File file = new File(dataDirectory, "linkedkilldata.yml");
        if (file.exists()) {
            file.delete();
        }
        Map<String, Integer> map = new LinkedHashMap<>();
        LinkedKillCount.forEach((uuid, count) -> map.put(uuid.toString(), count));
        writeAllText(file, new Yaml().dumpAs(map, Tag.MAP, DumperOptions.FlowStyle.BLOCK));
    }

    @SuppressWarnings("ResultOfMethodCallIgnored")
    private static void savePointData() throws Exception {
        if (!PluginRoot.exists()) {
            PluginRoot.mkdir();
        }
        File dataDirectory = new File(PluginRoot, "data");
        if (!dataDirectory.exists()) {
            dataDirectory.mkdir();
        }
        File file = new File(dataDirectory, "pointdata.yml");
        if (file.exists()) {
            file.delete();
        }
        Map<String, Integer> map = new LinkedHashMap<>();
        Points.forEach(((uuid, points) -> map.put(uuid.toString(), points)));
        writeAllText(file, new Yaml().dumpAs(map, Tag.MAP, DumperOptions.FlowStyle.BLOCK));
    }

    public static int getLinkedKillCountOf(Player player) {
        UUID uuid = player.getUniqueId();
        return LinkedKillCount.getOrDefault(uuid, 0);
    }

    public static void addLinkedKill(Player player) {
        UUID uuid = player.getUniqueId();
        if (LinkedKillCount.containsKey(uuid)) {
            LinkedKillCount.replace(uuid, LinkedKillCount.get(uuid) + 1);
        } else {
            LinkedKillCount.put(uuid, 1);
        }
    }

    public static void resetLinkedKill(Player player) {
        UUID uuid = player.getUniqueId();
        LinkedKillCount.remove(uuid);
    }

    public static List<Action> linkedKillActionsOfCount(int count) {
        return LinkedKillActions.getOrDefault(count, new ArrayList<>());
    }

    private static void writeAllText(File file, String content) throws Exception {
        FileWriter fileWriter = new FileWriter(file);
        fileWriter.write(content);
        fileWriter.close();
    }

    public static void debug() {
        Actions.forEach((s, action) -> System.out.println("Action '" + s + "': " + action));
        LinkedKillActions.forEach(((integer, actions) -> actions.forEach(action -> System.out.println(integer + "->" + action.getActionId()))));
        System.out.println(DistantKillLevel);

        Bukkit.getWorlds().forEach(world -> world.getPlayers().forEach(player ->
                System.out.println(player.getName() + "'s points: " + Point.get(player))));
    }

    public static BukkitTask invoke(Runnable runnable) {
        return Scheduler.runTask(Plugin, runnable);
    }

    public static boolean isMeleeWeapon(Material material) {
        return MeleeWeapons.contains(material);
    }

    public static void checkPlayerJoinPoints(Player player) {
        UUID uuid = player.getUniqueId();
        if (!Points.containsKey(uuid)) {
            Points.put(uuid, 0);
        }
    }

    public static int getPoints(Player player) {
        return Points.get(player.getUniqueId());
    }

    public static void addPoints(Player player, int points) {
        UUID uuid = player.getUniqueId();
        Points.replace(uuid, Points.get(uuid) + points);
    }

    public static void setPoints(Player player, int points) {
        UUID uuid = player.getUniqueId();
        Points.replace(uuid, points);
    }

    public static String rankOf(Player player) {
        if (RankLevel.isEmpty()) {
            return "未拥有军衔";
        }
        int points = getPoints(player);
        if (points < RankLevel.get(0).getKey()) {
            return "未拥有军衔";
        }
        int n = RankLevel.size();
        int i = 1;
        while (i < n && points > RankLevel.get(i).getKey()) {
            ++i;
        }
        return RankLevel.get(i - 1).getValue();
    }

    public static int rankIndexOf(Player player) {
        if (RankLevel.isEmpty()) {
            return -1;
        }
        int points = getPoints(player);
        if (points < RankLevel.get(0).getKey()) {
            return -1;
        }
        int n = RankLevel.size();
        int i = 1;
        while (i < n && points > RankLevel.get(i).getKey()) {
            ++i;
        }
        return i - 1;
    }

    @Override
    public void onEnable() {
        Logger = getLogger();
        Plugin = this;
        Server = getServer();
        Scheduler = getServer().getScheduler();
        getCommand("fms").setExecutor(new MainCommand());
        getCommand("ref").setExecutor(new DebugCommand());
        getCommand("points").setExecutor(new PointsCommand());

        getServer().getPluginManager().registerEvents(new BukkitListener(), this);
        getLogger().info("是插件注册了Forge事件，所以Forge核心找不到注册事件的mod，并发出警告。");
        getLogger().info("显然，这个警告可以忽略。");
        MinecraftForge.EVENT_BUS.register(new PlayerSlainListener());
        MinecraftForge.EVENT_BUS.register(new SoliderDeathListener());
        MinecraftForge.EVENT_BUS.register(new BFMCKeyInputListener());
        MinecraftForge.EVENT_BUS.register(new PlayerSupplyListener());
        MinecraftForge.EVENT_BUS.register(new PlayerReviveListener());
        MinecraftForge.EVENT_BUS.register(new AircraftDamageListener());
        MinecraftForge.EVENT_BUS.register(new AircraftDestroyListener());
        if (setupEconomy()) {
            Logger.info("Vault Hook 加载成功，经济系统已启用。");
        } else {
            Logger.info("Vault Hook 加载失败，这意味着经济系统将不会运行。");
        }
        if (PluginRoot.exists()) {
            //noinspection ResultOfMethodCallIgnored
            PluginRoot.mkdir();
        }
        loadConfigurations();
        loadData();
        loadItems();
    }

    @Override
    public void onDisable() {
        saveData();
    }

    private boolean setupEconomy() {
        if (getServer().getPluginManager().getPlugin("Vault") == null) {
            return false;
        }
        RegisteredServiceProvider<Economy> rsp = getServer().getServicesManager().getRegistration(Economy.class);
        if (rsp == null) {
            return false;
        }
        VaultEconomy = rsp.getProvider();
        return VaultEconomy != null;
    }
}
